//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "flashctrl.h"

#define HwFlashBase         0x00000000
#define HwFlashInitOffset   0x00600000
#define HwFlashInitSize     0x01a00000
#define HwUserSpaceBase     0x0

EXTERN_C int MTFlashEraseSector(
        unsigned long FlashBase, long Offset);

EXTERN_C int MTFlashWriteBuffer(
        unsigned long FlashBase, long Offset,
        unsigned char *Buffer, long BufferSize);

EXTERN_C int MTFlashReadBuffer(
        unsigned long FlashBase, long Offset,
        unsigned char *Buffer, long BufferSize);

//
// Flash
//
class Flash : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    ECode Initialize(Address physbase, UInt32 offset, UInt32 size);

private:
    Address     m_physbase;
    Address     m_virtbase;
    UInt32      m_offset;
    UInt32      m_size;
    static IDeviceDriver *m_pFlash;
    friend IDeviceDriver *CreateFlash(uint_t uDeviceNo, void *pvParameter);
};

IDeviceDriver* Flash::m_pFlash = NULL;


#define ERASE_UNIT_SIZE     (2 * 128 * 1024)    // 256K
#define FLASH_SIZE          0x02000000          // 32M

ECode Flash::Initialize(Address physbase, UInt32 offset, UInt32 size)
{
    m_physbase = physbase;
    m_virtbase = 0;
    m_offset = offset;
	m_size = size;

	m_physbase += m_offset;

	return NOERROR;
}

ECode Flash::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || !pBuffer->GetCapacity()
        || (int)bytesToRead > pBuffer->GetCapacity()
        || 0 == bytesToRead
        || m_size < u64Offset + bytesToRead) {
        return E_INVALID_ARGUMENT;
    }

//    kprintf("Reading 0x%x, 0x%x, 0x%x, 0x%x ...\n",
//        (unsigned long)m_virtbase,
//        (long)u64Offset,
//        (unsigned char *)(char *)ebbData,
//        bytesToRead);

    MTFlashReadBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)pBuffer->GetPayload(),
        bytesToRead);

    pBuffer->SetUsed(bytesToRead);

    return NOERROR;
}

ECode Flash::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (buffer.IsNullOrEmpty()
        || m_size < u64Offset + buffer.GetUsed()
        || NULL == pBytesWritten) {
         return E_INVALID_ARGUMENT;
    }

//    kprintf("Writing 0x%x, 0x%x, 0x%x, 0x%x ...\n",
//        (unsigned long)m_virtbase,
//        (long)u64Offset,
//        (unsigned char *)(char *)buffer,
//        (long)buffer.GetUsed());

    MTFlashWriteBuffer(
        (unsigned long)m_virtbase,
        (long)u64Offset,
        (unsigned char *)buffer.GetPayload(),
        (long)buffer.GetUsed());

    *pBytesWritten = (UInt32)buffer.GetUsed();

    return NOERROR;
}

ECode Flash::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;
    FlashInfo *pInfo;
    UInt32 offset;
    Address userSpaceVirtBase;

    switch (nControlCode) {
    case FlashControl_Open:
        /* DzIoRemap Should only appear at drivers initialization*/
        /*ec = DzIoRemap(0, m_size, m_physbase, 0, MemoryProtection_RWIO, &m_virtbase);
        if (FAILED(ec)) {
            m_virtbase = 0;
            return ec;
        }
        m_virtbase = (Address)DzKIoRemap(m_physbase, m_size);
        if (0 == m_virtbase) {
            kprintf("KIoRemap failed\n");
            return E_FAIL;
        }

        assert(0 != m_virtbase);
        kprintf("Mapped 0x%x to 0x%x\n", m_physbase, m_virtbase);*/
        break;

    case FlashControl_EraseSector:
        if (sizeof(UInt32) != (size_t)inBuffer.GetUsed()) {
            return E_INVALID_ARGUMENT;
        }

        offset = *(UInt32 *)inBuffer.GetPayload();
        offset = RoundDown2(offset, ERASE_UNIT_SIZE);

//        kprintf("Erasing 0x%x, 0x%x ...\n",
//                (unsigned long)m_virtbase, offset);
        MTFlashEraseSector((unsigned long)m_virtbase, offset);

        break;

    case FlashControl_GetInfo:
        if (pOutBuffer == NULL ||
            sizeof(FlashInfo) > (size_t)pOutBuffer->GetCapacity()) {
            return E_INVALID_ARGUMENT;
        }

        pInfo = (FlashInfo *)pOutBuffer->GetPayload();
        pInfo->Size = m_size;
        pInfo->EraseUnitSize = ERASE_UNIT_SIZE;
        pOutBuffer->SetUsed(sizeof(FlashInfo));

        break;

    case FlashControl_MapToUserSpace:
        ec = DzIoRemap(HwUserSpaceBase, m_size, m_physbase, 0, MemoryProtection_RWIO, &userSpaceVirtBase);
        if (FAILED(ec)) {
            return ec;
        }
        if (pOutBuffer && (pOutBuffer->GetCapacity() > sizeof(Address))) {
            *(Address *)pOutBuffer->GetPayload() = userSpaceVirtBase;
            pOutBuffer->SetUsed(sizeof(Address));
        }
        break;

    default:
        return E_INVALID_OPTIONS;
    }

    return NOERROR;
}

void Flash::Dispose()
{
    delete this;
}

EXTERN IDeviceDriver * CDECL CreateFlash(uint_t uDeviceNo, void *pvParameter)
{
    Flash *pFlash;
    ECode ec;

    if (Flash::m_pFlash != NULL) {
        Flash::m_pFlash->AddRef();
        return Flash::m_pFlash;
    }
    pFlash = new Flash();
    if (NULL == pFlash) return NULL;

    ec = pFlash->Initialize(HwFlashBase, HwFlashInitOffset, HwFlashInitSize);
    if (FAILED(ec)) {
        delete pFlash;
        return NULL;
    }

    pFlash->m_virtbase = (Address)DzKIoRemap(pFlash->m_physbase, pFlash->m_size);
    if (0 == pFlash->m_virtbase) {
        kprintf("KIoRemap failed\n");
        delete pFlash;
        return NULL;
    }

    assert(0 != pFlash->m_virtbase);
    kprintf("Mapped 0x%x to 0x%x\n", pFlash->m_physbase, pFlash->m_virtbase);

    pFlash->AddRef();
    Flash::m_pFlash = pFlash;
    return pFlash;
}

